#!/usr/local/bin/python
# Rob Dennis - Lurchington Q. Delacroix
# Keasby - TV renamer
# Project Homepage: http://code.google.com/p/keasby/

#--- Python imports
import os
import logging
import urllib2
import random
import shelve
import anydbm
import time
import re
import unittest
import sys
import collections
from random import choice
from contextlib import closing
from os.path import join as pjoin

try:
    import elementtree.ElementTree as ET
except ImportError:
    import sys
    sys.path.append(pjoin("thirdparty", "ElementTree"))
    import elementtree.ElementTree as ET


#--- Keasby Imports
from keasby_base import KeasbyTestCase

from keasby_toolbox import log

class TvDbSchema(collections.MutableMapping):
    def __contains__(self, key):
        return key in self.__dict__ and self.__dict__[key] is not None
    def __len__(self):
        return len(self.__dict__)
    def __setitem__(self, key, value):
        self.__dict__[key] = value
        return self
    def __getitem__(self, key):
        return self.__dict__[key]
    def __delitem__(self, key):
        del self.__dict__[key]
    def __iter__(self):
        return self.__dict__.__iter__()
    def __str__(self):
        return " ".join(["%s=%s"%(key, value) for key, value
                            in self.__dict__.iteritems()])



class TvDbSeries(TvDbSchema):
    def __init__(self, **kwargs):
        self.__dict__ =  dict.fromkeys(["id",
                                        "Actors",
                                        "Airs_DayOfWeek",
                                        "Airs_Time",
                                        "ContentRating",
                                        "FirstAired",
                                        "Genre",
                                        "IMDB_ID",
                                        "Language",
                                        "Network",
                                        "NetworkID",
                                        "Overview",
                                        "Rating",
                                        "Runtime",
                                        "SeriesID",
                                        "SeriesName",
                                        "Status",
                                        "added",
                                        "addedBy",
                                        "banner",
                                        "fanart",
                                        "lastupdated",
                                        "poster",
                                        "zap2it_id"])
        self.__dict__.update(kwargs)



class TvDbEpisode(TvDbSchema):
    def __init__(self, **kwargs):

        self.__dict__ =  dict.fromkeys(["id",
                                        "Combined_episodenumber",
                                        "Combined_season",
                                        "DVD_chapter",
                                        "DVD_discid",
                                        "DVD_episodenumber",
                                        "DVD_season",
                                        "Director",
                                        "EpImgFlag",
                                        "EpisodeName",
                                        "EpisodeNumber",
                                        "FirstAired",
                                        "GuestStars",
                                        "IMDB_ID",
                                        "Language",
                                        "Overview",
                                        "ProductionCode",
                                        "Rating",
                                        "SeasonNumber",
                                        "Writer",
                                        "absolute_number",
                                        "filename",
                                        "lastupdated",
                                        "seasonid",
                                        "seriesid"])
        self.__dict__.update(kwargs)


_TvDbTestItem = collections.namedtuple("_TvDbTestItem",
                                        ["series",
                                        "episode",
                                        "paths"])
class TvDbApi(object):
    """
    This class is written for interacting with thetvdb.com's API
    """

    base_url = "http://thetvdb.com"

    def __init__(self, api_key):
        """
        Instantiate's class attributes for caching, interaction with data,
        and for the initialization of mirrors and updates since last
        interaction.
        """

        self.api_key = api_key
        self._init_mirrors()
        self._init_language()
        self._init_last_update_time()

#---Initialization

    def _init_mirrors(self):
        """
        Downloads the available mirrors.xml file, and then populates
        the mirrors as xml, banner, or zip, or a combination of any or
        all
        """

        self.mirrors = dict(xml=[self.base_url],
                            banner=[self.base_url],
                            zip=[self.base_url])

    def _init_language(self):
        """
        Currently defaults to English, but could change
        """

        self.language = u"en"

    def _init_last_update_time(self):
        """
        Downloads and stores the most recent updates.xml
        """

        self.update_time = time.gmtime(0)

#---URL Interaction

    @staticmethod
    def _link_query(query_string):
        """
        Returns a generic TVDB query url
        """

        return TvDbApi._make_url(TvDbApi.base_url, "api", query_string)

    @staticmethod
    def open_query(query_string):
        """
        Returns an open file-like object for the content of query
        """

        return TvDbApi._url_open(TvDbApi._link_query(query_string))

    def _link_query_sid(self, seriesname):
        """
        Checks to see if showname has alredy been mapped to a series ID.
        If so, return it, otherwise query the showname and return the possible
        choices
        """

        if not isinstance(seriesname, basestring):
            raise TvDbApiError("Series name must be unicode or string")

        _escaped_showname = self._scrub(seriesname)

        return self._link_query("GetSeries.php?seriesname=%s"%_escaped_showname)

    def open_query_sid(self, seriesname):
        """
        Returns an open file-like object for the content of query_sid
        """

        return self._url_open(self._link_query_sid(seriesname))

    def _link_fsr(self, series_id):
        """
        returns a URL to the full zip-file for a given series_id
        """

        series_id_path = ("series",
                            self._scrub(series_id),
                            "all",
                            "%s.zip"%self.language)

        return self._get_api_url(self.get_mirror("zip"), *series_id_path)

    def open_fsr(self, series_id):
        """
        Returns an open file-like object for the content of \
        _link_fsr
        """

        return self._url_open(self._link_fsr(series_id))

    def _link_fsr_xml(self, series_id):
        """
        returns a URL to the full XML file for a given series_id
        """

        series_id_path = ("series",
                            self._scrub(series_id),
                            "all",
                            "%s.xml"%self.language)

        return self._get_api_url(self.get_mirror("xml"), *series_id_path)

    def open_fsr_xml(self, series_id):
        """
        Returns an open file-like object for the content of \
        _link_fsr_xml
        """

        return self._url_open(self._link_fsr_xml(series_id))

    def _link_banners_xml(self, series_id):
        """
        returns a URL to the banners.xml file for a given series_id
        """

        series_id_path = ("series",
                            self._scrub(series_id),
                            "banners.xml")

        return self._get_api_url(self.get_mirror("xml"), *series_id_path)

    def open_banners_xml(self, series_id):
        """
        Returns an open file-like object for the content of \
        _link_banners_xml
        """

        return self._url_open(self._link_banners_xml(series_id))

    def _link_bsr(self, series_id):
        """
        returns a URL to the base XML for a given series_id
        """

        series_id_path = ("series",
                            self._scrub(series_id),
                            "%s.xml"%self.language)

        return self._get_api_url(self.get_mirror("xml"), *series_id_path)

    def open_bsr(self, series_id):
        """
        Returns an open file-like object for the content of \
        _link_bsr
        """

        return self._url_open(self._link_bsr(series_id))

    def _link_ber(self, episode_id):
        """
        returns a URL to the base episode record given an episode_id
        """

        episode_id_path = ("episodes",
                            self._scrub(episode_id),
                            "%s.xml"%self.language)

        return self._get_api_url(self.get_mirror("xml"), *episode_id_path)

    def open_ber(self, episode_id):
        """
        Returns an open file-like object for the content of \
        _link_ber
        """

        return self._url_open(self._link_ber(episode_id))


    def _link_def_order_er(self, series_id,
                                                   seasonnumber,
                                                   episodenumber):
        """
        returns a URL to a base episode record given a season number and\
        episode number using the default order
        """

        er_path = ("series",
                    self._scrub(series_id),
                    "default",
                    self._scrub(seasonnumber),
                    self._scrub(episodenumber),
                    "%s.xml"%self.language)

        return self._get_api_url(self.get_mirror("xml"), *er_path)

    def open_def_order_er(self, series_id, seasonnumber, episodenumber):
        """
        Returns an open file-like object for the content of \
        _link_def_order_er
        """

        return self._url_open(self._link_def_order_er(series_id,
                                                     seasonnumber,
                                                     episodenumber))


    def _link_dvd_order_er(self, series_id, seasonnumber, episodenumber):
        """
        returns a URL to a base episode record given a season number and\
        episode number using DVD order
        """

        er_path = ("series",
                    self._scrub(series_id),
                    "dvd",
                    self._scrub(seasonnumber),
                    self._scrub(episodenumber),
                    "%s.xml"%self.language)

        return self._get_api_url(self.get_mirror("xml"), *er_path)

    def open_dvd_order_er(self, series_id, seasonnumber, episodenumber):
        """
        Returns an open file-like object for the content of \
        _link_dvd_order_er
        """

        return self._url_open(self._link_dvd_order_er(series_id, seasonnumber,
                                                        episodenumber))

    def _link_abs_order_er(self, series_id, absolutenumber):
        """
        returns a URL to a base episode record given a season number and\
        episode number
        """

        er_path = ("series",
                    self._scrub(series_id),
                    "absolute",
                    self._scrub(absolutenumber),
                    "%s.xml"%self.language)

        return self._get_api_url(self.get_mirror("xml"), *er_path)

    def open_abs_order_er(self, series_id, absolutenumber):
        """
        Returns an open file-like object for the content of \
        _link_def_order_er
        """

        return self._url_open(self._link_abs_order_er(series_id, absolutenumber))

#---Yields

    @staticmethod
    def yield_ber(ber_xml):
        """
        takes an XML-representation of the TVDB base episode record shown here:\
        http://www.thetvdb.com/wiki/index.php/API:Base_Episode_Record\
        and yields each key and value
        """

        ber = TvDbApi._cast_ET(ber_xml, u"Episode")

        for epinfo in ber.iter():
            yield epinfo.tag, epinfo.text

    @staticmethod
    def yield_bsr(bsr_xml):
        """
        takes an XML-representation of the TVDB base series record shown here:\
        http://www.thetvdb.com/wiki/index.php/API:Base_Series_Record\
        and yields each key and value
        """

        bsr = TvDbApi._cast_ET(bsr_xml, u"Series")

        for seriesinfo in bsr.iter():
            yield seriesinfo.tag, seriesinfo.text

    @staticmethod
    def yield_fsr(fsr_xml):
        """
        takes an XML-representation of the TVDB base series record shown here:\
        http://www.thetvdb.com/wiki/index.php/API:Full_Series_Record\
        and yields each key and value
        """

        fsr = TvDbApi._cast_ET(fsr_xml)

        bsr_iter = TvDbApi.yield_bsr(fsr)

        for episode in fsr.getiterator("Episode"):
            yield bsr_iter, TvDbApi.yield_ber(episode)


    @staticmethod
    def yield_query_sid(sid_xml):
        """
        takes in an XML-representation of the TVDB GetSeries query shown here:\
        http://www.thetvdb.com/wiki/index.php/API:GetSeries\
        and yields each key and value
        """

        sid_options = TvDbApi._cast_ET(sid_xml)

        for sid_option in sid_options.getiterator("Series"):
            yield TvDbApi.yield_bsr(sid_option)

#---Utility

    @staticmethod
    def search(xml, *path_elements, **search_pairs):
        """
        returns the elements existing in the provided XML with all the given\
        search_pairs (tag_name == text_value).
        """

        assert search_pairs, "calling search with nothing to search for"

        if log.getEffectiveLevel() <= logging.debug:
            log.debug("Searching with:")
            for key, value in search_pairs.iteritems():
                log.debug("%s = %s", key, value)


        search_iter = search_pairs.iteritems()
        orig_tag, orig_text = search_iter.next()

        if path_elements:
            xpath = r"/".join(path_elements)
            findall_path = r"%s/%s"%(xpath, orig_tag)
        else:
            findall_path = r".//%s/.."%orig_tag

        log.debug("searching using: %s"%findall_path)

        xml_et = TvDbApi._cast_ET(xml)

        if log.getEffectiveLevel() <= logging.DEBUG:
            for element in xml_et.findall(findall_path):
                #ET.dump(element)
                log.debug("found:")
                for key, value in search_pairs.iteritems():
                    log.debug("%s <=> %s", key, element.findtext(key))

        reduced_xml = [element for element in xml_et.findall(findall_path)
                        if element.findtext(orig_tag) == orig_text]

        if not reduced_xml:
            log.warning("no matches found from search %s for %s",
                        orig_tag, orig_text)

        if not search_iter:
            return reduced_xml

        for tag, text in search_iter:
            reduced_xml = [element for element in reduced_xml
                                if element.findtext(tag) == text]

        return reduced_xml

    def get_mirror(self, mirror_type):
        """
        Return a mirror url of the appropriate type, defaults to the base url
        if an unknown or uninitialized type is specified
        """

        return choice(self.mirrors.get(mirror_type, self.base_url))

    @staticmethod
    def _cast_ET(xml, parent_node=None):
        """
        if the provided XML conforms to the ET.Element protocol, return it\
        otherwise, parse and return it
        """

        casted_xml = None

        if isinstance(xml, basestring):
            log.debug("pre-casting basestring as xml")
            xml = ET.fromstring(xml)

        if isinstance(xml, ET.Element):
            casted_xml = xml
        elif isinstance(xml, ET.ElementTree):
            casted_xml = xml.getroot()
        else:
            log.debug("attempting to XML-parse provided %s",
                        xml.__class__.__name__)
            casted_xml = ET.parse(xml).getroot()

        if casted_xml is None:
            raise TvDbApiError("unable to get a valid XML element tree from"+
                                "%s"%xml.__class__.__name__)

        if parent_node is not None and casted_xml.tag != parent_node:
            found_xml = casted_xml.find(parent_node)
            if found_xml is None:
                raise TvDbApiError("unable to find parent node %s"%parent_node+
                                    "in xml:\n%s"%ET.dump(casted_xml))
            return found_xml

        return casted_xml






    @staticmethod
    def _scrub(component):
        """
        remove punctuation and other characters that won't be searched,
        also ensures the component is returned as a string or unicode (default)
        """

        _tmp = component

        if not isinstance(_tmp, basestring):
            _tmp = unicode(_tmp)

        _tmp = TvDbApi._escape_text(_tmp)

        return _tmp

    @staticmethod
    def _escape_text(text):
        """
        Used to escape potentially messed-up text used in URLs
        """

        return re.sub(r"\s+",r"%20",text)

    def _get_api_url(self, url_netloc=None, *url_path):
        """
        Convenience function for interacting accessing a URL relative to
        the tvdb API key
        """

        if url_netloc is None:
            url_netloc = self.base_url

        return TvDbApi._make_url(url_netloc, "api", self.api_key, *url_path)

    @staticmethod
    def _make_url(url_netloc, *url_path):
        """
        Appends all parts of url_path to the given url_netloc using "/" as
        a delimeter
        """

        url_sequence = url_netloc,
        url_sequence = url_sequence + url_path

        return "/".join(url_sequence)

    @staticmethod
    def _url_open(url):
        """
        return a file-like object with the contents of the given url
        """

        return urllib2.urlopen(url)


class TvDbApiError(Exception):
    """
    Generic TvDbApi Exception
    """
    pass

class TvDbApiTestCase(KeasbyTestCase):
    """
    Tests the use of the TvDbApi class
    """

    a = TvDbApi("D1EBA6781E2572BB")
    sanity = a

    test_dir = os.path.join(sys.path[0], "test", "tvdb")

    #A default episode to test against
    test_def = _TvDbTestItem(TvDbSeries(SeriesName=u"Lost", id=u"73739"),
                             TvDbEpisode(id=u"127131", EpisodeName=u"Pilot (1)",
                                         SeasonNumber="1", EpisodeNumber="1"),
                             dict(fsr_zip=pjoin(test_dir,
                                                "Lost-73739-FSR.zip"),
                                  fsr_xml=pjoin(test_dir,
                                                "Lost-73739-FSR.xml"),
                                  bsr_xml = pjoin(test_dir,
                                                    "Lost-73739-bsr.xml"),
                                  ber_xml = pjoin(test_dir,
                                                  "Lost-73739-127131-ber.xml"),
                                  query_xml = pjoin(test_dir,
                                                    "Lost-73739-query.xml")))




    #Not all shows have a mismatch between airing (default) and DVD order
    #this is one
    test_dvd = _TvDbTestItem(TvDbSeries(SeriesName=u"Firefly", id=u"78874"),
                             TvDbEpisode(id=u"297999", EpisodeName=u"Serenity",
                                         SeasonNumber="1", EpisodeNumber="11",
                                         DVD_season="1",
                                         DVD_episodenumber="1.0"),
                             dict(fsr_zip=pjoin(test_dir,
                                                "Firefly-78874-FSR.zip"),
                                  fsr_xml=pjoin(test_dir,
                                                "Firefly-78874-FSR.xml"),
                                  bsr_xml = pjoin(test_dir,
                                                    "Firefly-78874-bsr.xml"),
                                  ber_xml = pjoin(test_dir,
                                                "Firefly-78874-297999-ber.xml"),
                                  query_xml = pjoin(test_dir,
                                                    "Firefly-78874-query.xml")))


    #Not all shows have absolutenumber present, so here's a test for that:
    #Bleach is the example from: http://thetvdb.com/wiki/index.php/abs_order
    test_abs = _TvDbTestItem(TvDbSeries(SeriesName=u"Bleach", id=u"74796"),
                             TvDbEpisode(id=u"155071",
                                    EpisodeName=u"The Day I Became a Shinigami",
                                    absolute_number="1"),
                             dict(fsr_zip=pjoin(test_dir,
                                                "Bleach-74796-FSR.zip"),
                                  fsr_xml=pjoin(test_dir,
                                                "Bleach-74796-FSR.xml")
                                                ))

    #selecting a show with a basic enough name to ensure several options
    test_query_sid = test_def.episode.id
    test_query_name = test_def.series.SeriesName

    def setUp(self):
        """
        Instantiates a KeasbyOptions instance
        """

    def test_get_mirrors(self):
        """
        Tests for proper return of mirrors
        """

        for mirror_type in ["xml", "zip", "banner"]:
            self.assertTrue(self.a.get_mirror(mirror_type)
                                in self.a.mirrors[mirror_type],
                            "error retrieving mirror of type %s"%mirror_type)

    def test_internal_initialization(self):
        """
        tests for the proper initialization of class attributes and resources
        """

        test_url = r"http://thetvdb.com"

        for mirror_type, mirror_list in self.a.mirrors.iteritems():
            self.assertLog(test_url in mirror_list,
                            "%s not in %s list"%(test_url,mirror_type))

            self.assertLog(isinstance(self.a.update_time, time.struct_time))

    def test_linking(self):
        """
        tests for results of the linking functions
        """

        _link_tests = []
        _link_tests.append((self.a._link_query_sid,
                            (self.test_query_name,),
                            {},
                            "series_id query"))
        _link_tests.append((self.a._link_abs_order_er,
                            (self.test_abs.episode.id,
                             self.test_abs.episode.absolute_number),
                            {},
                            "absolute"))
        _link_tests.append((self.a._link_banners_xml,
                            (self.test_def.series.id,),
                            {},
                            "banners"))
        _link_tests.append((self.a._link_ber,
                            (self.test_def.episode.id,),
                            {},
                            "base episode"))
        _link_tests.append((self.a._link_bsr,
                            (self.test_def.series.id,),
                            {},
                            "base series"))
        _link_tests.append((self.a._link_def_order_er,
                            (self.test_def.series.id,
                                self.test_def.episode.SeasonNumber,
                                self.test_def.episode.EpisodeNumber),
                            {},
                            "default order"))
        _link_tests.append((self.a._link_dvd_order_er,
                            (self.test_dvd.series.id,
                                self.test_dvd.episode.DVD_season,
                                self.test_dvd.episode.DVD_episodenumber),
                            {},
                            "dvd order"))
        _link_tests.append((self.a._link_fsr,
                            (self.test_def.series.id,),
                            {},
                            "full series"))
        _link_tests.append((self.a._link_fsr_xml,
                            (self.test_def.series.id,),
                            {},
                            "full series xml"))

        for test_link, args, kwargs, name in _link_tests:
            self.assertLog(test_link(*args, **kwargs),
                            fail_message = "invalid response from %s"%name,
                            pass_message = " ".join(["received %s"%name,
                                                    "url: %(expression)s"]))


    def __test_opening(self):
        """
        tests for results of the opening functions
        """

        open_tests = []
        open_tests.append((self.a.open_query_sid,
                            (self.test_query_name,),
                            {},
                            "series_id query"))
        open_tests.append((self.a.open_abs_order_er,
                            (self.test_abs.episode.id,
                             self.test_abs.episode.absolute_number),
                            {},
                            "absolute"))
        open_tests.append((self.a.open_banners_xml,
                            (self.test_def.series.id,),
                            {},
                            "banners"))
        open_tests.append((self.a.open_ber,
                            (self.test_def.episode.id,),
                            {},
                            "base episode"))
        open_tests.append((self.a.open_bsr,
                            (self.test_def.series.id,),
                            {},
                            "base series"))
        open_tests.append((self.a.open_def_order_er,
                            (self.test_def.series.id,
                                self.test_def.episode.SeasonNumber,
                                self.test_def.episode.EpisodeNumber),
                            {},
                            "default order"))
        open_tests.append((self.a.open_dvd_order_er,
                            (self.test_dvd.series.id,
                                self.test_dvd.episode.DVD_season,
                                self.test_dvd.episode.DVD_episodenumber),
                                {},
                            "dvd order"))
        open_tests.append((self.a.open_fsr,
                            (self.test_def.series.id,),
                            {},
                            "full series"))
        open_tests.append((self.a.open_fsr_xml,
                            (self.test_def.series.id,),
                            {},
                            "full series xml"))
        for test_open, args, kwargs, name in open_tests:
            try:
                url_contents = test_open(*args, **kwargs)

                self.assertLog(url_contents,
                                pass_message = " ".join(["opened %s"%name,
                                                            "url: %(url)s"]),
                                pass_message_kwargs = dict(url=
                                                        url_contents.geturl()))

                url_contents.close()

            except urllib2.HTTPError, msg:
                self.fail("%s - %s"%(name, msg))

    def __conduct_yield_comparison(self, iterator, testitem):
        """
        Helper method to simplify comparisons
        """

        for key, value in iterator:
            if key in testitem:
                self.assertLog(testitem[key]==value,
                                pass_message="%(k)s <=> %(v)s",
                                pass_message_kwargs=dict(k=key, v=value),
                                fail_message="%(k)s:%(v1)s%(t1)s!=%(v2)s%(t2)s",
                                fail_message_kwargs=dict(k=key,
                                                         v1=value,
                                                         t1=type(value),
                                                         v2=testitem[key],
                                                         t2=type(testitem[key]))
                                                         )
            elif testitem.get(key, False) is not None:
                log.debug("key %s not in %s. Found \"%s\" %s", key,
                                              testitem.__class__.__name__,
                                              value,
                                              type(value))
    def test_yield_bsr(self):
        """
        tests the yield functions for the correct iteration of a given TVDB-\
        provided XML document
        """

        for test_ep in [self.test_def, self.test_dvd]:
            with open(test_ep.paths["bsr_xml"], "rb") as bsr_xmlobj:
                log.debug("yielding base series record from: %s",
                            test_ep.paths["bsr_xml"])

                self.__conduct_yield_comparison(self.a.yield_bsr(bsr_xmlobj),
                                                test_ep.series)

    def test_yield_ber(self):
        """
        tests the yield functions for the correct iteration of a given TVDB-\
        provided XML document
        """

        for test_ep in [self.test_def, self.test_dvd]:
            with open(test_ep.paths["ber_xml"], "rb") as ber_xmlobj:
                log.debug("yielding base episode record from: %s",
                            test_ep.paths["ber_xml"])

                self.__conduct_yield_comparison(self.a.yield_ber(ber_xmlobj),
                                                test_ep.episode)

    def test_yield_query(self):
        """
        tests the yield functions for the correct iteration of a given TVDB-\
        provided XML document
        """

        for test_ep in [self.test_def, self.test_dvd]:
            match_count = 0
            with open(test_ep.paths["query_xml"], "rb") as xmlobj:
                log.debug("yielding series id query information from: %s",
                            test_ep.paths["query_xml"])

                for count, series in enumerate(self.a.yield_query_sid(xmlobj)):
                    option_map = dict(series)
                    log.debug("option %d: %s", count+1,
                                               option_map["SeriesName"])

                    matches = []
                    for key in [u"SeriesName", "id"]:
                        matches.append(test_ep.series[key]==option_map[key])

                    if all(matches):
                        log.debug("found a match")
                        match_count += 1

                    self.assertTrue(match_count==1,
                                    "error found %d matches"%match_count)

    def test_yield_fsr(self):
        """
        tests the yield functions for the correct iteration of a given TVDB-\
        provided XML document
        """

        for test_ep in [self.test_def, self.test_dvd, self.test_abs]:
            with open(test_ep.paths["fsr_xml"], "rb") as fsr_xmlobj:
                log.debug("yielding series id query information from: %s",
                            test_ep.paths["fsr_xml"])

                for series, episode in self.a.yield_fsr(fsr_xmlobj):
                    for skey, svalue in series:
                        #log.debug("%s <=> %s %s", skey, svalue, type(svalue))
                        if skey in test_ep.series:
                            self.assertEquals(svalue, test_ep.series[skey],
                                          " ".join(["Incorrect %s"%skey,
                                                    "found: %s"%svalue,
                                          "(!= %s)"%test_ep.series[skey],
                                          "\n%s"%str(test_ep.series)]))
                    for ekey, evalue in episode:
                        pass
                        #log.debug("%s <=> %s %s", ekey, evalue, type(evalue))

    def test_search(self):
        """
        tests search for specific elements
        """
        for test_ep in [self.test_def, self.test_dvd]:

            series_name = test_ep.series.SeriesName
            series_id = test_ep.series.id

            with open(test_ep.paths["query_xml"], "rb") as sid_xmlobj:

                results = list(self.a.search(sid_xmlobj,
                                        SeriesName=series_name,
                                        id=series_id))

                self.assertTrue(results, "No results found in search")

                log.debug("%d results found"%len(results))

                self.assertTrue(all([(element.findtext("SeriesName")==
                                        series_name)
                                        for element in results]),
                                        "series name not matched")
                self.assertTrue(all([(element.findtext("id")==
                                        series_id)
                                        for element in results]),
                                        "series id not matched")


if __name__ == "__main__":
    suite = unittest.TestSuite()
    suite.addTest(unittest.makeSuite(TvDbApiTestCase))
    unittest.TextTestRunner(verbosity=2).run(suite)